--- title: Generating Calibration Files for the OpenHSI Camera keywords: fastai sidebar: home_sidebar summary: "A notebook example for generating settings and calibration files for the OpenHSI camera." description: "A notebook example for generating settings and calibration files for the OpenHSI camera." nb_path: "nbs/10_tutorial_calibrate.ipynb" ---
{% raw %}
{% endraw %} {% raw %}
{% endraw %}

{% include warning.html content='This is not for genral use. Requires technical expertise.' %} Tools required:

  • An integrating sphere (we use a Spectra PT from LabSphere)
  • A HgAr lamp

Adjust the camera class as needed. This example uses the LucidCamera.

{% raw %}
 
{% endraw %} {% raw %}
import os

import holoviews as hv
import numpy as np

from openhsi.calibrate import SettingsBuilderMixin, SpectraPTController, sum_gaussians
from openhsi.cameras import XimeaCameraBase
from openhsi.capture import OpenHSI

hv.extension("bokeh", logo=False)
import panel as pn


class CalibrateCamera(SettingsBuilderMixin, XimeaCameraBase, OpenHSI):
    pass

json_path_template = "/xavier_ssd/cals/cam_settings_ximea_template.json"
pkl_path = ""

modelno = 1

print("".format(modelno))

json_path_target = "/xavier_ssd/cals/StarSHOT-HSI-{0:02d}/StarSHOT-HSI-{0:02d}_settings_Mono8_bin1.json".format(
    modelno
)
pkl_path_target = "/xavier_ssd/cals/StarSHOT-HSI-{0:02d}/StarSHOT-HSI-{0:02d}_calibration_Mono8_bin1.pkl".format(
    modelno
)

if not os.path.isdir(os.path.dirname(json_path_target)):
    os.mkdir(os.path.dirname(json_path_target))

spt = SpectraPTController()

{% endraw %}

Find illuminated sensor area

The vertical directon/y axis of the detector array corrspeonds the across-track direction of the sensor. If the image of slit is shorter then the heigh we can crop the top and bottom to save bandwidth/disk space (similar to letterboxing video).

There are two ways to do this, croping after the fact using row_minmax or by setting up a window on the sensor. Setting up a window will reduce the ammount of data transfered from the sensor and can improve maximum framerate depending on the sensor so is recomended.

1. Take a flat field

First step is to provide a uniform illumination to the slit, ideally spectrally broadband, like a halogen lamp or the sun.

{% raw %}
with CalibrateCamera(
    json_path=json_path_template, pkl_path="", processing_lvl=-1, exposure_ms=10
) as cam:

    hvim_flat = cam.retake_flat_field(show=True)
    hvim_flat.opts(width=600, height=600, axiswise=True)

#     hvim_row_minmax = cam.update_row_minmax(edgezone=10)
#     hvim_row_minmax.opts(width=600, height=600, axiswise=True)

#     # Use Row min max to update window
#     windowheight = int(
#         np.ceil((cam.settings["row_slice"][1] - cam.settings["row_slice"][0]) / 4.0) * 4
#     )
#     print("Windowheight {}".format(windowheight))

#     cam.settings["win_resolution"] = [windowheight + 16, cam.settings["resolution"][1]]
#     cam.settings["win_offset"] = [
#         int(np.ceil((cam.settings["row_slice"][0]) / 4.0) * 4) - 8,
#         cam.settings["win_offset"][1],
#     ]

#     cam.settings["row_slice"] = [16, windowheight - 8]

    cam.settings["resolution"] = cam.settings["win_resolution"]
    cam.dump(json_path=json_path_target, pkl_path=pkl_path_target)
#     img=cam.crop(cam.calibration["flat_field_pic"])
#     hv_flatcrop=hv.Image(img, bounds=(0,0,*img.shape)).opts(xlabel="wavelength index",ylabel="cross-track",cmap="gray",title="test frame",width=400,height=400,axiswise=True)

# spt.selectPreset(1000)
# pn.Column(hvim_row_minmax, hvim_flat)
xiAPI: ---- xiOpenDevice API:V4.27.07.00 started ----
Allocated 81.13 MB of RAM. There was 11540.43 MB available.
xiAPI: EAL_IF_xiFAPI_Top::InitializeDevice sn:CEMAU2105019 name:MC031MG-SY-UB
xiAPI: FGTL_SetParam_to_CAL error from CAL: -1015, addr:x27317e
xiAPI: XiApiToGentlParamModel Auto bandwidth measurement finished (396MBps). Safe limit set to: 317MBps
xiAPI: FGTL_SetParam_to_CAL error from CAL: -10009, addr:x201380
xiAPI: ---- Device opened. Model:MC031MG-SY-UB SN:CEMAU2105019 FwF1:01.31 API:V4.27.07.00 ----
Connected to device b'CEMAU2105019'
xiAPI: xiFAPI_Device::AllocateBuffers Allocating buffers. Count:134 OneBuff:1949 KiB All:256 MiB Frm:x10c0047
xiAPI: xiAPI error: Expected XI_OK in:../API/xiFAPI/camera_model/XiApiToGentlParamModel.cpp GetHDR/Line:622
xiAPI: xiFAPI_Device::AcquisitionStop - ignored: acquisition is not running
xiAPI: xiCloseDevice
{% endraw %} {% raw %}
with CalibrateCamera(
    n_lines=50,
    processing_lvl=0,
    pkl_path=pkl_path_target,
    json_path=json_path_target,
    exposure_ms=10,
) as cam:
    # cam.collect()
    cam.start_cam()
    img = cam.get_img()
    img = cam.crop(img)
    cam.stop_cam()
    # cam.show(hist_eq=True)

hv.Image(img, bounds=(0, 0, *img.shape)).opts(
    xlabel="wavelength index",
    ylabel="cross-track",
    cmap="gray",
    title="test frame",
    width=400,
    height=400,
)
xiAPI: ---- xiOpenDevice API:V4.27.07.00 started ----
Allocated 237.78 MB of RAM. There was 11430.97 MB available.
xiAPI: EAL_IF_xiFAPI_Top::InitializeDevice sn:CEMAU2105019 name:MC031MG-SY-UB
xiAPI: FGTL_SetParam_to_CAL error from CAL: -1015, addr:x27317e
xiAPI: XiApiToGentlParamModel Auto bandwidth measurement finished (396MBps). Safe limit set to: 317MBps
xiAPI: FGTL_SetParam_to_CAL error from CAL: -10009, addr:x201380
xiAPI: ---- Device opened. Model:MC031MG-SY-UB SN:CEMAU2105019 FwF1:01.31 API:V4.27.07.00 ----
Connected to device b'CEMAU2105019'
xiAPI: xiFAPI_Device::AllocateBuffers Allocating buffers. Count:134 OneBuff:1949 KiB All:256 MiB Frm:x10c0047
xiAPI: xiAPI error: Expected XI_OK in:../API/xiFAPI/camera_model/XiApiToGentlParamModel.cpp GetHDR/Line:622
xiAPI: xiFAPI_Device::AcquisitionStop - ignored: acquisition is not running
xiAPI: xiCloseDevice
{% endraw %}

2. Take Arc and setup wavelength scale, and get window for 430 to 900nm

{% raw %}
with CalibrateCamera(
    json_path=json_path_target, 
    pkl_path="", 
    processing_lvl=-1, 
    exposure_ms=10
) as cam:
    # cam.deviceSettings["Gain"].value = 10.0
    hvimg = cam.retake_HgAr(show=True, nframes=18)
    # hvimg=hv.Image(cam.crop(cam.calibration["HgAr_pic"]), bounds=(0,0,*cam.calibration["HgAr_pic"].shape)).opts(
    #                 xlabel="wavelength index",ylabel="cross-track",cmap="gray",title="HgAr spectra picture")

    hvimg.opts(width=600, height=600)
#     print(cam.calibration["HgAr_pic"].max())
#     smile_fit_hv = cam.update_smile_shifts()

#     # cam.deviceSettings['Gain'].value=15.
#     # hvimg=cam.retake_HgAr(show=True,nframes=18)

#     cam.calibration["smile_shifts"] = cam.calibration["smile_shifts"] * 0

#     wavefit_hv = cam.fit_HgAr_lines(
#         top_k=15,
#         brightest_peaks=[546.96, 435.833, (579.960 + 579.066) / 2, 763.511],
#         find_peaks_height=10,
#         prominence=1,
#         width=1.5,
#         interactive_peak_id=True,
#     )  # [435.833,546.074,,763.511]

#     # Use wavecal to narrow spetral range.
#     waveminmax = [430, 900]
#     waveminmax_ind = [
#         np.argmin(np.abs(cam.calibration["wavelengths_linear"] - λ)) for λ in waveminmax
#     ]

#     window_width = int(np.ceil((waveminmax_ind[1] - waveminmax_ind[0] + 8) / 4.0) * 4)
#     offset_x = int(np.floor((waveminmax_ind[0] - 4) / 4.0) * 4)
#     print("Window Width {}, offset x {}".format(window_width, offset_x))

#     cam.settings["win_resolution"][1] = window_width
#     cam.settings["win_offset"][1] = offset_x
#     cam.settings["resolution"] = cam.settings["win_resolution"]

#     pn.Column(
#         hvimg,
#         smile_fit_hv,
#         wavefit_hv.opts(xlim=(390, 1000), ylim=(-10, 255)).opts(shared_axes=False),
#     )
Allocated 31.45 MB of RAM. There was 11637.15 MB available.
xiAPI: ---- xiOpenDevice API:V4.27.07.00 started ----
xiAPI: EAL_IF_xiFAPI_Top::InitializeDevice sn:CEMAU2105019 name:MC031MG-SY-UB
xiAPI: FGTL_SetParam_to_CAL error from CAL: -1015, addr:x27317e
xiAPI: XiApiToGentlParamModel Auto bandwidth measurement finished (396MBps). Safe limit set to: 317MBps
xiAPI: FGTL_SetParam_to_CAL error from CAL: -10009, addr:x201380
xiAPI: ---- Device opened. Model:MC031MG-SY-UB SN:CEMAU2105019 FwF1:01.31 API:V4.27.07.00 ----
Connected to device b'CEMAU2105019'
xiAPI: xiFAPI_Device::AllocateBuffers Allocating buffers. Count:346 OneBuff:756 KiB All:256 MiB Frm:x10c0047
xiAPI: xiAPI error: Expected XI_OK in:../API/xiFAPI/camera_model/XiApiToGentlParamModel.cpp GetHDR/Line:622
xiAPI: xiFAPI_Device::AcquisitionStop - ignored: acquisition is not running
xiAPI: xiCloseDevice
{% endraw %} {% raw %}
hvimg
{% endraw %} {% raw %}
pn.Column(
    hvimg.opts(shared_axes=False),
    smile_fit_hv.opts(shared_axes=False),
    wavefit_hv.opts(xlim=(400, 900), ylim=(-10, 255)).opts(shared_axes=False),
)
{% endraw %} {% raw %}
cam.dump(json_path=json_path_target, pkl_path=pkl_path_target)
{% endraw %}

3. Retake flat field and arc with windows

{% raw %}
spt.selectPreset(10000)

with CalibrateCamera(
    json_path=json_path_target, pkl_path=pkl_path_target, processing_lvl=-1
) as cam:
    hvim_flat = cam.retake_flat_field(show=True)
    hvim_flat.opts(width=600, height=600, axiswise=True)

    hvim_row_minmax = cam.update_row_minmax(edgezone=8)
    hvim_row_minmax.opts(width=600, height=600, axiswise=True)

    cam.update_resolution()
    cam.dump(json_path=json_path_target, pkl_path=pkl_path_target)

# spt.turnOffLamp()

hvim_row_minmax + hvim_flat
{% endraw %}

Redo Arc with window.

{% raw %}
with CalibrateCamera(
    json_path=json_path_target, pkl_path=pkl_path_target, processing_lvl=-1
) as cam:
    cam.deviceSettings["Gain"].value = 15.0
    hvimg = cam.retake_HgAr(show=True)

    hvimg.opts(width=400, height=400)
    print(cam.calibration["HgAr_pic"].max())
    smile_fit_hv = cam.update_smile_shifts()

    wavefit_hv = cam.fit_HgAr_lines(
        top_k=12,
        brightest_peaks=[546.96, 435.833, (579.960 + 579.066) / 2, 871.66, 763.511],
        find_peaks_height=10,
        prominence=1,
        width=1.5,
        max_match_error=2,
        interactive_peak_id=True,
    )  # [435.833,546.074,(579.960+579.066)/2,763.511]
    
    cam.update_intsphere_fit()

    cam.dump(json_path=json_path_target, pkl_path=pkl_path_target)

(hvimg + smile_fit_hv + wavefit_hv.opts(xlim=(400, 900), ylim=(-10, 255))).opts(
    shared_axes=False
)
{% endraw %}

3. Get Integrating Sphere data for radiance calibration

4D datacube with coordinates of cross-track, wavelength, exposure, and luminance.

{% raw %}
lum_preset_dict = {
    0: 1,
    1000: 2,
    2000: 3,
    3000: 4,
    4000: 5,
    5000: 6,
    6000: 7,
    7000: 8,
    8000: 9,
    9000: 10,
    10000: 11,
    20000: 12,
    25000: 13,
    30000: 15,
    35000: 15,
    40000: 16,
}

lum_preset_dict = {
    0: 1,
    1000: 2,
    2000: 3,
    4000: 5,
    6000: 7,
    8000: 9,
    10000: 11,
    20000: 12,
    40000: 16,
}

luminances = np.fromiter(lum_preset_dict.keys(), dtype=int)
# luminances = np.append(luminances,0)
exposures = [0, 5, 8, 10, 15, 20]

with CalibrateCamera(
    json_path=json_path_target, pkl_path=pkl_path_target, processing_lvl=-1
) as cam:

    cam.calibration["rad_ref"] = cam.update_intsphere_cube(
        exposures, luminances, noframe=50, lum_chg_func=spt.selectPreset
    )

    # remove saturated images
    cam.calibration["rad_ref"] = cam.calibration["rad_ref"].where(
        ~(
            np.sum((cam.calibration["rad_ref"][:, :, :, :, :] == 255), axis=(1, 2))
            > 1000
        )
    )
    cam.dump(json_path=json_path_target, pkl_path=pkl_path_target)

spt.turnOffLamp()
{% endraw %} {% raw %}
cam.calibration["rad_ref"].plot(
    y="cross_track", x="wavelength_index", col="exposure", row="luminance", cmap="gray"
)
{% endraw %} {% raw %}
print("rad_ref is {} MB".format(cam.calibration["rad_ref"].size / 1024 / 1024 * 4))
{% endraw %} {% raw %}
cam.update_intsphere_fit()
cam.dump(json_path=json_path_target, pkl_path=pkl_path_target)
{% endraw %}